/**
	License:		Artistic license 2.0 (see license.txt, also available online at http://www.perlfoundation.org/artistic_license_2_0)
	Authors:		OrbitalLab (http://www.orbitallab.ru/moonglide/), 2007-2009

	File:			SceneManager.d
	Description:	manager of scenes: keeps scenes, runs active scene and processes it layer by layer to irenderer
	Date:			12.11.2007 by Digited
*/
module moonglide.scenes.SceneManager;

import
	tango.core.Memory,

	moonglide.util.MoonUtil,

	moonglide.core.EngineCore,
//	moonglide.core.EngObjects,

	moonglide.renderer.IRenderer,
	moonglide.scenes.IScene;

import
	moonglide.core.engobjects.EngObject,
	moonglide.core.engobjects.EngSprite,
	moonglide.core.engobjects.EngCamera,
	moonglide.core.engobjects.EngLight,
	moonglide.core.engobjects.EngGeometry,
	moonglide.core.engobjects.EngRawData,
	moonglide.core.engobjects.EngParSystem;

//==============================================================
/// Scene manager class - keeps scenes, runs and processes active to irenderer
//==============================================================
class CSceneManager
{
	protected:
		this() {}

		IScene[ char[] ]	_scenes;
		char[]				_activeSceneID;
		IScene				_activeSceneObject;
		IRenderer			_engRenderer;

		static CSceneManager _singleton;

		SRawData			_tempRawData;

	public:
		static CSceneManager opCall()
		{
			if( !_singleton )
				_singleton = new CSceneManager();

			return _singleton;
		}

		//==============================================================
		/// returns array of registered scenes' names
		//==============================================================
		char[][] scenes()
		{
			return _scenes.keys;
		}

		//==============================================================
		/// adds new scene, if it doesn't exist. Doesn't make new scene active - must do it manually
		//==============================================================
		bool addScene( IScene newscene )
		{
			if( sceneExists( newscene.id ) )
				return false;

			_scenes[ newscene.id ] = newscene;
			debug debugLog.trace( "Scene added: " ~ newscene.id );
			return true;
		}

		//================================================================
		/// checks if the scene with id is registered (presents in _scenes associative array)
		//================================================================
		bool sceneExists( char[] id )
		{
			if( !_scenes.length )
				return false;

			return ( id in _scenes ) != null;
		}

		//================================================================
		/// get scene pointer if exists
		//================================================================
		IScene getScene( char[] id )
		{
			return sceneExists( id ) ? _scenes[ id ] : null;
		}

		//=============================================================
		/// get active scene
		//=============================================================
		IScene getActiveScene()
		{
			return _activeSceneObject;
		}

		//================================================================
		/// deletes scene. If it was active, active is set to null.
		//================================================================
		bool deleteScene( char[] id )
		{
			if( !sceneExists( id ) )
				return false;

			if( _activeSceneID == id )
			{
				_activeSceneID = "";
				_activeSceneObject = null;
				CEngineCore().unRegisterListenerAll( _scenes[ id ] );
			}

			debug debugLog.trace( "Scene deleted: " ~ id );

			_scenes.remove( id );
			return true;
		}

		//================================================================
		/// sets active scene if id is registered. Does NOT shutdown previous active scene - do it manually, if needed
		//================================================================
		bool setActiveScene( char[] id )
		{
			if( !_engRenderer )		// moved here because that code is sure called with valid core & renderer
				_engRenderer = CEngineCore().renderer;

			bool result = false;

			if( sceneExists( id ) && ( id != _activeSceneID ))
			{
				// check existance of all resource files, mentioned in scene config
				auto scene = _scenes[ id ];
				assert( scene );
				scene.loadConfig();
				CConfig sc = scene.getConfig();

				char[][] missingFiles;

				if( sc )
				{
					foreach( res; sc.mStringFields.keys )
						if( res.length > 4 && res[0..4] == "res_" )
						{
							auto fp = new FilePath( sc.mStringFields[res] );

							if( !fp.exists )
								missingFiles ~= res;
						}
				}

				if( missingFiles.length )
				{
					errorLog( "cannot set active scene [" ~ id ~ "] because of missing resource files:" );

					foreach( name; missingFiles )
						errorLog( name );
				}
				else
				{
					traceLog.trace( "Active scene is set: " ~ id );

					_activeSceneID = id.dup;
					_activeSceneObject = scene;
					CEngineCore().registerListenerAll( scene );
					result = true;
				}
			}

			return result;
		}

		//=============================================================
		/// sets active scene with optional adding (if not registered yet)
		//=============================================================
		bool setActiveScene( IScene scene )
		{
			if( !scene || ( scene is null ))
				return false;

			addScene( scene );
			return setActiveScene( scene.id );
		}

		//==============================================================
		/// processes active scene and feeds it layer by layer to irenderer
		//==============================================================
		void processActiveScene( float time )
		{
			if( _activeSceneID.length != 0 )
			{
				if( _activeSceneObject is null )
				{
					debug debugLog.trace( "Scene in active scenes map is null: " ~ _activeSceneID );
					return;
				}

				/** The perfomance of realtime rendering applications most often depends on productivity of
				video card, also the most important parameter of realtime rendering is "fps", frames per second rate.
				Frame rendering is being done between sending rendering data to video card and swapping
				buffers. When we call SwapBuffers() in OpenGL or D3D, we actually WAIT until video card will
				finish ALL the rendering of that frame. Anything between swapping buffers to present last frame
				and sending data of new frame is WASTE OF TIME, because videocard does nothing at that time.
				All realtime rendering applications are fps-critical, so if we want the highest fps possible,
				everything else MUST be done BETWEEN sending data and swapping buffers.

				The usual scheme of rendering is:
				1. run system cycle
				2. process scene, physics, ai etc
				3. send rendering data to video card
				4. swap buffers
				Using that scheme, 1 and 2 are waisted for fps and lower fps rate!

				So, the right order of rendering is:
				1. Sending previos frame rendering data to videocard
				2. system messages cycle, scene run for current(next) frame rendering data, all events, all cpu processing
				3. swapping buffers.
				Using that scheme, we have as little processing between 3 to 1 as possible, and highest fps possible
				because of that.

				IMPORTANT: The scene processing code below starts with scene run and swapping buffers.
				That may look confusing, but is done for making MoonGlide's rendering order right.
				The order is:
				1. run system cycle
				2. process scene, events etc
				3. swap buffers to present last frame
				4. send rendering data of new frame to video card
				As we see, that IS a right order, do not change it!
				*/
				_activeSceneObject.run( time );
				_engRenderer.presentFrame();

				// rendering starts
				_engRenderer.startScene();

				foreach( index, layer; _activeSceneObject.layers )
				{
//					_engRenderer.offscreenMan.setRenderTarget( layer.renderTarget );
					_engRenderer.setActiveCamera( layer.camera );

//-------------------------------------------------------------------------------------------
						void recursiveAddNod( CEngCamera activeCamera, CEngObject obj )
						{
							if( activeCamera.isInFrustum( obj ) )
							{
								obj.onRender();

								if( cast(CEngRawData)obj )
									_tempRawData += (cast( CEngRawData )obj).data;
								else
								{
									_engRenderer.xRawData ~= _tempRawData;
									_tempRawData.clear;

									if( cast(CEngParSystem)obj )
										_engRenderer.xParSys ~= cast(CEngParSystem)obj;
									else if( cast(CEngLight)obj )
										_engRenderer.xLight ~= cast(CEngLight)obj;
									else if( cast(CEngSprite)obj )
										_engRenderer.xSpr ~= cast(CEngSprite)obj;
									else if( cast(CEngGeometry)obj )
										_engRenderer.xGeom ~= cast(CEngGeometry)obj;
								}

								foreach( inout child; obj.children )
									recursiveAddNod( activeCamera, child );
							}
						}
//-------------------------------------------------------------------------------------------

					// fill list
					foreach( sceneObject; layer.children )
						recursiveAddNod( layer.camera, sceneObject );

					// light. bruteforce
					foreach( inout geom; _engRenderer.xGeom )
					{
						geom.lights.length = 0;

						foreach( inout li; _engRenderer.xLight )
						{

					//		if ( li.radius == 0 )
								geom.lights ~= li;
						//	else
							//	if(( li.wposition - geom.wposition ).normSquare() < ( li.radius * li.radius ))
								//	geom.lights ~= li;
						}
					}

					_engRenderer.render();
				}
			}
		}


		//==============================================================
		/// clear stuff. Shutdowns and deletes active scene too.
		//==============================================================
		void clearAllScenes()
		{
			if( _scenes.length == 0 )
				return;

			if( _activeSceneID.length > 0 )
				CEngineCore().unRegisterListenerAll( _scenes[ _activeSceneID ] );

			foreach( inout sc; _scenes )
			{
				sc.shutdown();
				delete sc;
			}
			_scenes = null;

			debug debugLog.trace( "All scenes cleared!" );

            char[][] notDeletedObj = CEngObject.listObj;
            if(  notDeletedObj.length > 0 )
            {
                while( notDeletedObj.length > 0 )
                {

                    char[] strId = notDeletedObj[0];
                    attentionLogR( strId );

                    CEngObject o = CEngObject.getObject( strId );
                    delete o;
                    notDeletedObj = CEngObject.listObj;
                }

                attentionLogR("Objects, that was without layer, has been deleted");
                debug consolePause;
            }

			_activeSceneID = "";
			_activeSceneObject = null;

			moonAssert( _scenes.length == 0, "", __FILE__, __LINE__ );
		}

		//==============================================================
		/// destructor
		//==============================================================
		~this() { clearAllScenes(); }


		debug void DbgReport()
		{
			debugLogY( "----------------" );
			debugLogY( "SceneManager. begin of report" );
			debugLogY( "File: <" ~ __FILE__ ~ ">" );
			debugLogY( "----------------" );

//---------------------------------------------------------------------------------------
				void recursDBGNode( CEngObject obj, char[] Pref )
				{
					if( obj is null )
					{
						debugLogR("NULL!");
						return;
					}

                    if( obj.children.length>0 )
                        debugLogB( Format( "{}'s children.length = {}", Pref ~ obj.id, obj.children.length ));
                    else
                        debugLogB( Pref ~ obj.id );

					Pref ~= " ";

					foreach( child; obj.children )
						recursDBGNode( child, Pref );
				}
//---------------------------------------------------------------------------------------

			debugLog.trace("Scenes:");

			foreach( inout sc; _scenes )
			{
				debugLogB("---");
				debugLogB( "SCENE: " ~ sc.id );

				foreach( layer; sc.layers )
				{
					debugLogG( "Layer " ~ layer.id );

					foreach( child; layer.children )
						recursDBGNode( child, "" );
				}

				debugLogB("---");
			}

			debugLogY("SceneManager. end of report");
			debugLogY("----------------");
		}
}
